"""
主服务器文件 - FastAPI 应用和路由定义

此文件包含 FastAPI 应用实例、中间件、路由处理函数和应用启动逻辑。
所有具体的业务逻辑都已移至专门的模块中。
"""

import json
import logging
import time
import uvicorn
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import StreamingResponse

# 导入配置
from config import ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY

# 导入日志配置
from logger_config import setup_logging, log_request_beautifully

# 导入数据模型
from models import MessagesRequest, TokenCountRequest, TokenCountResponse

# 导入转换器
from converters import convert_anthropic_to_litellm, convert_litellm_to_anthropic

# 导入流式处理
from streaming import handle_streaming

# 导入第三方库
import litellm
from dotenv import find_dotenv, load_dotenv
load_dotenv(find_dotenv(), override=True)

# 设置日志
logger = setup_logging()

# 创建 FastAPI 应用
app = FastAPI()


@app.middleware("http")
async def log_requests(request: Request, call_next):
    # 获取请求详细信息
    method = request.method
    path = request.url.path
    
    # 在调试级别仅记录基本请求详细信息
    logger.debug(f"请求：{method} {path}")
    
    # 处理请求并获取响应
    response = await call_next(request)
    
    return response


# 不使用验证函数，因为我们使用环境 API 密钥


@app.post("/v1/messages")
async def create_message(
    request: MessagesRequest,
    raw_request: Request
):
    try:
        # 在这里打印主体
        body = await raw_request.body()
    
        # 将原始主体解析为 JSON，因为它是字节
        body_json = json.loads(body.decode('utf-8'))
        original_model = body_json.get("model", "unknown")
        
        # 获取用于日志记录的显示名称，只是没有提供商前缀的模型名称
        display_model = original_model
        if "/" in display_model:
            display_model = display_model.split("/")[-1]
        
        # 清理模型名称以进行能力检查
        clean_model = request.model
        if clean_model.startswith("anthropic/"):
            clean_model = clean_model[len("anthropic/"):]
        elif clean_model.startswith("openai/"):
            clean_model = clean_model[len("openai/"):]
        
        logger.debug(f"📊 处理请求：模型={request.model}，流式={request.stream}")
        
        # 将 Anthropic 请求转换为 LiteLLM 格式
        litellm_request = convert_anthropic_to_litellm(request)
        
        # 根据模型确定使用哪个 API 密钥
        if request.model.startswith("openai/"):
            litellm_request["api_key"] = OPENAI_API_KEY
            logger.debug(f"为模型使用 OpenAI API 密钥：{request.model}")
        elif request.model.startswith("gemini/"):
            litellm_request["api_key"] = GEMINI_API_KEY
            logger.debug(f"为模型使用 Gemini API 密钥：{request.model}")
        else:
            litellm_request["api_key"] = ANTHROPIC_API_KEY
            logger.debug(f"为模型使用 Anthropic API 密钥：{request.model}")
        
        # 对于 OpenAI 模型 - 修改请求格式以适应限制
        if "openai" in litellm_request["model"] and "messages" in litellm_request:
            logger.debug(f"处理 OpenAI 模型请求：{litellm_request['model']}")
            
            # 对于 OpenAI 模型，我们需要将内容块转换为简单字符串
            # 并处理其他要求
            for i, msg in enumerate(litellm_request["messages"]):
                # 特殊情况 - 当消息内容是 tool_result 列表时直接处理
                # 这是我们在错误中看到的特定情况
                if "content" in msg and isinstance(msg["content"], list):
                    is_only_tool_result = True
                    for block in msg["content"]:
                        if not isinstance(block, dict) or block.get("type") != "tool_result":
                            is_only_tool_result = False
                            break
                    
                    if is_only_tool_result and len(msg["content"]) > 0:
                        logger.warning(f"发现仅包含 tool_result 内容的消息 - 需要特殊处理")
                        # 从所有 tool_result 块中提取内容
                        all_text = ""
                        for block in msg["content"]:
                            all_text += "工具结果：\n"
                            result_content = block.get("content", [])
                            
                            # 处理不同格式的内容
                            if isinstance(result_content, list):
                                for item in result_content:
                                    if isinstance(item, dict) and item.get("type") == "text":
                                        all_text += item.get("text", "") + "\n"
                                    elif isinstance(item, dict):
                                        # 回退到任何字典的字符串表示
                                        try:
                                            item_text = item.get("text", json.dumps(item))
                                            all_text += item_text + "\n"
                                        except:
                                            all_text += str(item) + "\n"
                            elif isinstance(result_content, str):
                                all_text += result_content + "\n"
                            else:
                                try:
                                    all_text += json.dumps(result_content) + "\n"
                                except:
                                    all_text += str(result_content) + "\n"
                        
                        # 用提取的文本替换列表
                        litellm_request["messages"][i]["content"] = all_text.strip() or "..."
                        logger.warning(f"将 tool_result 转换为纯文本：{all_text.strip()[:200]}...")
                        continue  # 跳过此消息的正常处理
                
                # 1. 处理内容字段 - 正常情况
                if "content" in msg:
                    # 检查内容是否为列表（内容块）
                    if isinstance(msg["content"], list):
                        # 将复杂内容块转换为简单字符串
                        text_content = ""
                        for block in msg["content"]:
                            if isinstance(block, dict):
                                # 处理不同的内容块类型
                                if block.get("type") == "text":
                                    text_content += block.get("text", "") + "\n"
                                
                                # 处理 tool_result 内容块 - 提取嵌套文本
                                elif block.get("type") == "tool_result":
                                    tool_id = block.get("tool_use_id", "unknown")
                                    text_content += f"[工具结果 ID：{tool_id}]\n"
                                    
                                    # 从 tool_result 内容中提取文本
                                    result_content = block.get("content", [])
                                    if isinstance(result_content, list):
                                        for item in result_content:
                                            if isinstance(item, dict) and item.get("type") == "text":
                                                text_content += item.get("text", "") + "\n"
                                            elif isinstance(item, dict):
                                                # 通过尝试提取文本或转换为 JSON 来处理任何字典
                                                if "text" in item:
                                                    text_content += item.get("text", "") + "\n"
                                                else:
                                                    try:
                                                        text_content += json.dumps(item) + "\n"
                                                    except:
                                                        text_content += str(item) + "\n"
                                    elif isinstance(result_content, dict):
                                        # 处理字典内容
                                        if result_content.get("type") == "text":
                                            text_content += result_content.get("text", "") + "\n"
                                        else:
                                            try:
                                                text_content += json.dumps(result_content) + "\n"
                                            except:
                                                text_content += str(result_content) + "\n"
                                    elif isinstance(result_content, str):
                                        text_content += result_content + "\n"
                                    else:
                                        try:
                                            text_content += json.dumps(result_content) + "\n"
                                        except:
                                            text_content += str(result_content) + "\n"
                                
                                # 处理 tool_use 内容块
                                elif block.get("type") == "tool_use":
                                    tool_name = block.get("name", "unknown")
                                    tool_id = block.get("id", "unknown")
                                    tool_input = json.dumps(block.get("input", {}))
                                    text_content += f"[工具：{tool_name}（ID：{tool_id}）]\n输入：{tool_input}\n\n"
                                
                                # 处理图像内容块
                                elif block.get("type") == "image":
                                    text_content += "[图像内容 - 不以文本格式显示]\n"
                        
                        # 确保 OpenAI 模型的内容永远不为空
                        if not text_content.strip():
                            text_content = "..."
                        
                        litellm_request["messages"][i]["content"] = text_content.strip()
                    # 还要检查 None 或空字符串内容
                    elif msg["content"] is None:
                        litellm_request["messages"][i]["content"] = "..." # 不允许空内容
                
                # 2. 删除 OpenAI 在消息中不支持的任何字段
                for key in list(msg.keys()):
                    if key not in ["role", "content", "name", "tool_call_id", "tool_calls"]:
                        logger.warning(f"从消息中删除不支持的字段：{key}")
                        del msg[key]
            
            # 3. 最终验证 - 检查任何剩余的无效值并转储完整的消息详细信息
            for i, msg in enumerate(litellm_request["messages"]):
                # 记录消息格式以进行调试
                logger.debug(f"消息 {i} 格式检查 - 角色：{msg.get('role')}，内容类型：{type(msg.get('content'))}")
                
                # 如果内容仍然是列表或 None，用占位符替换
                if isinstance(msg.get("content"), list):
                    logger.warning(f"严重：消息 {i} 在处理后仍有列表内容：{json.dumps(msg.get('content'))}")
                    # 最后手段 - 将整个内容字符串化为 JSON
                    litellm_request["messages"][i]["content"] = f"内容作为 JSON：{json.dumps(msg.get('content'))}"
                elif msg.get("content") is None:
                    logger.warning(f"消息 {i} 有 None 内容 - 用占位符替换")
                    litellm_request["messages"][i]["content"] = "..." # 回退占位符
        
        # 只记录请求的基本信息，而不是完整详细信息
        logger.debug(f"模型请求：{litellm_request.get('model')}，流式：{litellm_request.get('stream', False)}")
        
        # 处理流式模式
        if request.stream:
            # 使用 LiteLLM 进行流式传输
            num_tools = len(request.tools) if request.tools else 0
            
            log_request_beautifully(
                "POST", 
                raw_request.url.path, 
                display_model, 
                litellm_request.get('model'),
                len(litellm_request['messages']),
                num_tools,
                200  # 假设此时成功
            )
            import os
            litellm_request['base_url'] = os.getenv('OPENAI_BASE_URL')
            # 确保我们使用异步版本进行流式传输
            response_generator = await litellm.acompletion(**litellm_request)
            
            return StreamingResponse(
                handle_streaming(response_generator, request),
                media_type="text/event-stream"
            )
        else:
            # 使用 LiteLLM 进行常规完成
            num_tools = len(request.tools) if request.tools else 0
            
            log_request_beautifully(
                "POST", 
                raw_request.url.path, 
                display_model, 
                litellm_request.get('model'),
                len(litellm_request['messages']),
                num_tools,
                200  # 假设此时成功
            )
            start_time = time.time()
            litellm_response = litellm.completion(**litellm_request)
            logger.debug(f"✅ 收到响应：模型={litellm_request.get('model')}，时间={time.time() - start_time:.2f}秒")
            
            # 将 LiteLLM 响应转换为 Anthropic 格式
            anthropic_response = convert_litellm_to_anthropic(litellm_response, request)
            
            return anthropic_response
                
    except Exception as e:
        import traceback
        error_traceback = traceback.format_exc()
        
        # 尽可能多地捕获错误信息
        error_details = {
            "error": str(e),
            "type": type(e).__name__,
            "traceback": error_traceback
        }
        
        # 检查 LiteLLM 特定属性
        for attr in ['message', 'status_code', 'response', 'llm_provider', 'model']:
            if hasattr(e, attr):
                error_details[attr] = getattr(e, attr)
        
        # 检查字典中的其他异常详细信息
        if hasattr(e, '__dict__'):
            for key, value in e.__dict__.items():
                if key not in error_details and key not in ['args', '__traceback__']:
                    error_details[key] = str(value)
        
        # 记录所有错误详细信息
        logger.error(f"处理请求时出错：{json.dumps(error_details, indent=2)}")
        
        # 格式化响应错误
        error_message = f"错误：{str(e)}"
        if 'message' in error_details and error_details['message']:
            error_message += f"\n消息：{error_details['message']}"
        if 'response' in error_details and error_details['response']:
            error_message += f"\n响应：{error_details['response']}"
        
        # 返回详细错误
        status_code = error_details.get('status_code', 500)
        raise HTTPException(status_code=status_code, detail=error_message)


@app.post("/v1/messages/count_tokens")
async def count_tokens(
    request: TokenCountRequest,
    raw_request: Request
):
    try:
        # 记录传入的令牌计数请求
        original_model = request.original_model or request.model
        
        # 获取用于日志记录的显示名称，只是没有提供商前缀的模型名称
        display_model = original_model
        if "/" in display_model:
            display_model = display_model.split("/")[-1]
        
        # 清理模型名称以进行能力检查
        clean_model = request.model
        if clean_model.startswith("anthropic/"):
            clean_model = clean_model[len("anthropic/"):]
        elif clean_model.startswith("openai/"):
            clean_model = clean_model[len("openai/"):]
        
        # 将消息转换为 LiteLLM 可以理解的格式
        converted_request = convert_anthropic_to_litellm(
            MessagesRequest(
                model=request.model,
                max_tokens=100,  # 令牌计数中不使用的任意值
                messages=request.messages,
                system=request.system,
                tools=request.tools,
                tool_choice=request.tool_choice,
                thinking=request.thinking
            )
        )
        
        # 使用 LiteLLM 的 token_counter 函数
        try:
            # 导入 token_counter 函数
            from litellm import token_counter
            
            # 美观地记录请求
            num_tools = len(request.tools) if request.tools else 0
            
            log_request_beautifully(
                "POST",
                raw_request.url.path,
                display_model,
                converted_request.get('model'),
                len(converted_request['messages']),
                num_tools,
                200  # 假设此时成功
            )
            
            # 计数令牌
            token_count = token_counter(
                model=converted_request["model"],
                messages=converted_request["messages"],
            )
            
            # 返回 Anthropic 风格的响应
            return TokenCountResponse(input_tokens=token_count)
            
        except ImportError:
            logger.error("无法从 litellm 导入 token_counter")
            # 回退到简单近似
            return TokenCountResponse(input_tokens=1000)  # 默认回退
            
    except Exception as e:
        import traceback
        error_traceback = traceback.format_exc()
        logger.error(f"计数令牌时出错：{str(e)}\n{error_traceback}")
        raise HTTPException(status_code=500, detail=f"计数令牌时出错：{str(e)}")


@app.get("/")
async def root():
    return {"message": "LiteLLM 的 Anthropic 代理"}


if __name__ == "__main__":
    import sys
    if len(sys.argv) > 1 and sys.argv[1] == "--help":
        print("运行方式：uvicorn server:app --reload --host 0.0.0.0 --port 8082")
        sys.exit(0)
    
    # 配置 uvicorn 以最少日志运行
    uvicorn.run(app, host="0.0.0.0", port=8082, log_level="error")